#include <signal.h>
#include <functional>

#include "application.h"
#include "env.h"
#include "daemon.h"
#include "config.h"
#include "util.h"
#include "http/http_server.h"
#include "module.h"
#include "worker.h"
#include "http/session_data.h"

namespace qtch {

static Logger::ptr logger = QTCH_LOG_NAME("system");

static ConfigVar<std::string>::ptr g_server_work_path = Config::LookUp("server.work_path",std::string("/app/work/qtch"),"server work path");
static ConfigVar<std::string>::ptr g_server_pid_file = Config::LookUp("server.pid_file",std::string("qtch.pid"),"server pid file");
static ConfigVar<std::vector<TcpServerConf> >::ptr g_tcp_server_conf = Config::LookUp("tcp.servers",std::vector<TcpServerConf>(),"tcp server config");

Application* Application::m_application = nullptr;

Application::Application() {
    m_application = this;
}

void Application::addCommand(const std::string& command_name, std::function<int()> fun){
    auto it = m_commands_list.find(command_name);
    if(it != m_commands_list.end()){
        QTCH_LOG_WARN(logger) << "The command already exists and will be overwritten";
    }
    m_commands_list[command_name] = fun;
}

bool Application::init(int argc,char** argv) {
    EnvMgr::GetInstance()->addHelp("d","start with deamon");
    EnvMgr::GetInstance()->addHelp("c","config path default: ./conf");
    EnvMgr::GetInstance()->addHelp("h","print help");
    EnvMgr::GetInstance()->addHelp("s","command of {stop}");

    addCommand("stop",Application::stopRunning);

    bool isPrintHelp = false;
    if(!EnvMgr::GetInstance()->init(argc,argv)){
        isPrintHelp = true;
    }

    ModuleMgr::GetInstance()->init();
    std::vector<Module::ptr> modules;
    ModuleMgr::GetInstance()->listAll(modules);

    std::string conf_path = EnvMgr::GetInstance()->getConfigPath();
    QTCH_LOG_DEBUG(logger) << "load config path:" << conf_path;
    Config::LoadConfigDir(conf_path);

    for(auto& i: modules){
        i->onBeforeArgsParse(argc,argv);
    }

    if(EnvMgr::GetInstance()->has("h")){
        isPrintHelp = true;
    }

    if(isPrintHelp){
        EnvMgr::GetInstance()->printfHelp();
        return false;
    }

    if(EnvMgr::GetInstance()->has("s")){
        if(exec_command(EnvMgr::GetInstance()->get("s"))){
            QTCH_LOG_ERROR(logger) << "exec -s " << EnvMgr::GetInstance()->get("s") << " faile";
        }else {
            QTCH_LOG_INFO(logger) << "exec -s " << EnvMgr::GetInstance()->get("s") << " success";
        }

        return false;
    }
    
    for(auto& i: modules){
        i->onAfterArgsParse(argc,argv);
    }
    modules.clear();


    std::string pidFile = g_server_work_path->getValue() + "/" + g_server_pid_file->getValue();
    if(FSUtil::isRunningPidFile(pidFile)){
        QTCH_LOG_ERROR(logger) << "server is running:" << pidFile;
        return false;
    }

    if(!FSUtil::MkDir(g_server_work_path->getValue())){
        QTCH_LOG_ERROR(logger) << "create work path [" << g_server_work_path->getValue() << "]"
                << " errno=" << errno << " errstr=" << strerror(errno);
        return false;
    }

    return true;

}

int Application::exec_command(const std::string& command){
    if(command.empty()){
        QTCH_LOG_ERROR(logger) << "command could't be empty";
        return 1;
    }
    auto it = m_commands_list.find(command);
    if(it == m_commands_list.end()){
        QTCH_LOG_ERROR(logger) << "command '" << command <<"' not find";
        return 1;
    }
    if(it->second()){
        QTCH_LOG_DEBUG(logger) << "command:" << command << " exec faile";
        return 1;
    }
    return 0;

}

bool Application::run() {
    bool is_deamon = EnvMgr::GetInstance()->has("d");
    return start_daemon(m_argc,m_argv,
            std::bind(&Application::main,this,std::placeholders::_1,std::placeholders::_2)
            ,is_deamon);
}

void Application::stop() {
    QTCH_LOG_INFO(logger) << "applcation closing";
    for(auto it = m_application->m_tcpServers.begin(); it != m_application->m_tcpServers.end(); ++it){
        for(auto& p : it->second){
            p->stop();
        }
    }
    WorkerMgr::GetInstance()->stop();
}

int Application::stopRunning() {
    QTCH_LOG_INFO(logger) << "stop Running Application";
    std::string pidFile = g_server_work_path->getValue() + "/" + g_server_pid_file->getValue();
    if(!FSUtil::isRunningPidFile(pidFile)){
        QTCH_LOG_ERROR(logger) << "There is no Application running";
        return 1;
    }
    pid_t pid = FSUtil::getRunningPid(pidFile);
    if(pid < 1){
        return 1;
    }
    if(kill(pid,SIGTERM) != 0){
        return 1;
    }
    return 0;


}

static void ApplicationStop(int sig){
    QTCH_LOG_DEBUG(logger) << "ApplicationStop received signal SIGTERM";
    Application::stop();

}

int Application::main(int argc,char** argv) {
    signal(SIGPIPE, SIG_IGN);
    signal(SIGTERM,ApplicationStop);
    srand((unsigned)time(NULL));
    QTCH_LOG_DEBUG(logger) << "main";
    {
        std::string pidFile = g_server_work_path->getValue()+"/"
                                + g_server_pid_file->getValue();
        std::ofstream ofs(pidFile);
        if(!ofs){
            QTCH_LOG_ERROR(logger) << "open pidfile " << pidFile << " failed";
            return 0;
        }  
        ofs << getpid();
    }
    m_mainIOManager.reset(new IOManager(1,true,"main"));
    WorkerMgr::GetInstance()->add(m_mainIOManager);
    
    m_mainIOManager->addTimer(2000,[](){},true); // 保持一个最少的任务使得程序一直运行

    m_mainIOManager->schedule(std::bind(&Application::run_fiber,this));
    m_mainIOManager->start();

    return 0;
}

int Application::run_fiber() {
    std::vector<Module::ptr> moduleVec;
    ModuleMgr::GetInstance()->listAll(moduleVec);
    bool hasError = false;
    for(auto& i: moduleVec){
        if(!i->onLoad()){
            hasError = true;
            QTCH_LOG_ERROR(logger) << "module onLoad faile name=" << i->getName()
                << " version=" << i->getVersion()
                << " desc=" << i->getDesc()
                << " fileName=" << i->getFileName();
        }
    }
    if(hasError){
        _exit(0);
    }
    WorkerMgr::GetInstance()->init();
    std::vector<TcpServer::ptr> tcpSvrsVec;
    std::vector<TcpServerConf> tcpServerConfigVec = g_tcp_server_conf->getValue();
    for(auto& i: tcpServerConfigVec){
        QTCH_LOG_DEBUG(logger) << std::endl << LexicalCast<std::string,TcpServerConf>()(i);
        std::vector<Address::ptr> addres;
        for(auto& addr: i.address){
            size_t pos = addr.find_last_of(":");
            if(pos == std::string::npos){
                addres.push_back(UnixAddress::ptr(new UnixAddress(addr)));
                continue;
            }
            int port = TypeUtil::Atoi(addr.substr(pos+1).c_str());
            auto addr_ptr = IPAddress::Create(addr.substr(0,pos).c_str(),port);
            if(addr_ptr){
                addres.push_back(addr_ptr);
                continue;
            }
            QTCH_LOG_ERROR(logger) << "invalid adress addr=[" << addr << "] create failed";
            _exit(0);
        }
        
        IOManager * accept_worker = IOManager::getThis();
        IOManager * io_worker = IOManager::getThis();
        IOManager * process_work = IOManager::getThis();

        if(!i.accept_work.empty()){
            accept_worker = WorkerMgr::GetInstance()->getAsIOManager(i.accept_work).get();
            if(!accept_worker){
                QTCH_LOG_ERROR(logger) << "accept_worker: " << i.accept_work << " not exists";
                _exit(0);
            }
        }
        if(!i.io_work.empty()){
            io_worker = WorkerMgr::GetInstance()->getAsIOManager(i.io_work).get();
            if(!io_worker){
                QTCH_LOG_ERROR(logger) << "io_worker: " << i.io_work << " not exists";
                _exit(0);
            }
        }
        if(!i.process_work.empty()){
            process_work = WorkerMgr::GetInstance()->getAsIOManager(i.process_work).get();
            if(!process_work){
                QTCH_LOG_ERROR(logger) << "process_work: " << i.process_work << " not exists";
                _exit(0);
            }
        }

        TcpServer::ptr server;
        if(i.type=="http"){
            server.reset(new http::HttpServer(i.keeplive,process_work,io_worker,accept_worker));
        }else{
            QTCH_LOG_ERROR(logger) << "invalid server type=" << i.type;
            _exit(0);
        }
        if(!i.name.empty()){
            server->setName(i.name);
        }
        std::vector<Address::ptr> fails;
        if(!server->bind(addres,fails,i.ssl)){
            for(auto& fail_addr:fails){
                QTCH_LOG_ERROR(logger) << "bind address " << fail_addr << " faile" ;
            }
            _exit(0);
        }

        if(i.ssl){
            if(!server->loadCertificates(EnvMgr::GetInstance()->getAbsolutePath(i.cert_file),EnvMgr::GetInstance()->getAbsolutePath(i.key_file))){
                QTCH_LOG_ERROR(logger) << "loadCertificates fail, cert_file=" << i.cert_file << " key_file=" << i.key_file;
                _exit(0);
            }
        }
        server->setConf(i);
        m_tcpServers[i.type].push_back(server);
        tcpSvrsVec.push_back(server);
    }

    for(auto& i: moduleVec){
        if(!i->onServerReady()){
            hasError = true;
            QTCH_LOG_ERROR(logger) << "module onServerReady faile name=" << i->getName()
                << " version=" << i->getVersion()
                << " desc=" << i->getDesc()
                << " fileName=" << i->getFileName();
        }
    }
    if(hasError){
        _exit(0);
    }

    for(auto & it: tcpSvrsVec){
        it->start();
    }

    http::SessionDataMgr::GetInstance()->init();
    for(auto& i: moduleVec){
        if(!i->onServerUp()){
            hasError = true;
            QTCH_LOG_ERROR(logger) << "module onServerReady faile name=" << i->getName()
                << " version=" << i->getVersion()
                << " desc=" << i->getDesc()
                << " fileName=" << i->getFileName();
        }
    }
    if(hasError){
        _exit(0);
    }

    return 0;
}

bool Application::getTcpServer(const std::string& type,std::vector<TcpServer::ptr>& svrs) {
    auto it = m_tcpServers.find(type);
    if(it==m_tcpServers.end()){
        return false;
    }
    svrs = it->second;
    return true;
}

void Application::listAllTcpServer(std::map<std::string,std::vector<TcpServer::ptr> >& servers) {
    servers = m_tcpServers;
}



}